2019-10-15
下面表格是 Proxy 物件在各種瀏覽器版本中的支援程度, 資料參考來源從 caniuse 網站取得
Browser | Support Version |
---|---|
IE | |
Edge | 12-18, 76 |
Firefox | 18-68, 69 |
Chrome | 49-76, 77 |
Safari | 10-12.1, 13 |
Opera | 36-60, 62 |
iOS Safari | 10-12.3, 13.1 |
Opera Mini | All Not support |
Android Browser | 76 |
Opera Mobile | 46 |
Chrome for Android | 76 |
Firefox for Android | 68 |
UC Browser for Android | 12.12 |
Samsung Internet | 5-9.2, 10.1 |
QQ Browser | |
Baidu Browser | |
KaiOS Browser | 2.5 |
以上版本號碼有刪除的樣式, 表示不支援
Proxy 和Reflect 是ES6 新增的API, Proxy 是一個函式物件, 它提供一個機會讓你能介入一般物件的基本操作行為, 很像 interceptor 會做的事情一樣.
使用的方法如下
const proxyObj = new Proxy(target, handler);
以下是一個典型的 Proxy 範例, 示範了 cat 物件被代理成 catProxy 物件, 然後不能存取 cat 私有變數的方法.
let cat = {
_secret: 'I am Mr.Brain',
name: 'Flash'
};
let catProxy = new Proxy(cat, {
get: function (target, prop) {
if( prop.startsWith('_') ) {
console.log('不能存取私有變數');
return false;
}
return target[prop];
},
set: function (target, prop, value) {
if (prop.startsWith('_')) {
console.log('不能修改私有變數');
return false;
}
target[prop] = value;
},
has: function (target, prop) {
return prop.startsWith('_') ? false : (prop in target);
}
});
如果你的物件擁有 configurable: false 與 writable: false 的屬性,那該物件就無法被 proxy 代理, 如下示範
const target = Object.defineProperties({}, {
Cat: {
writable: false,
configurable: false
},
});
const handler = {
get(target, propKey) {
return '???';
}
};
const proxy = new Proxy(target, handler);
proxy.FooBar
一旦你按照上面的程式碼執行, 就會噴出這個例外錯誤
Uncaught TypeError: 'get' on proxy: property 'FooBar' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'undefined' but got '???')
我們在 Proxy 中, 如果需要 target 物件的預設操作, 使用 Reflect 會更清楚, 如下示範
const loggedObj = new Proxy(obj, {
get: function(target, name) {
console.log("get", target, name);
return Reflect.get(target, name);
}
});
以上是property 的proxy, 下面是method proxy 示範
let cat = {
name: "Kitty",
method1: function(msg){
console.log("cat: " + this.name + " say " + msg);
},
};
var catProxy = new Proxy(cat, {
get: function(target, propKey, receiver){
//我只要攔截方法(method calls), 不要屬性(property access)
var propValue = target[propKey];
if (typeof propValue != "function"){
return propValue;
}
else{
return function() {
console.log("intercepting call to " + propKey + " in cat " + target.name);
//"this" 指向 proxy, 就像"receiver"
return propValue.apply(this, arguments);
}
}
}
});
以上Proxy 的概念性, 在沒有ES6 語法之前, 使用Typescript 撰寫 "裝飾者模式" 就可以做到, 如下所示
interface ICat {
sayHello(msg: string): void;
}
class RealCat implements ICar {
public sayHello(msg: string): void {
console.log('RealCat: ' + msg);
}
}
class CatProxy implements ICat {
private _realCat: RealCat;
constructor(realCat: RealCat) {
this._realCat = realCat;
}
public sayHello(msg: string): void {
console.log("Hey! I am Kitty");
this._realCat.sayHello(msg);
}
}
let catProxy = new CatProxy(new RealCat());
catProxy.sayHello("Hello Proxy World");
使用上述的裝飾者模式時候, 假如 ICat 介面中的方法和屬性加起來有100 個, 你就得在 CatProxy 一一實作包裝並呼叫 _realCat.
但你又是個懶人不想一一手動去實作, 你可以用 Proxy 和 Reflect 來幫忙實作每一個 ICat 的公開方法和屬性, 如下示範Typescript 程式碼是如何做到這件事情
function fakeBaseClass<T>() : new() => Pick<T, keyof T> {
return class {} as any;
}
class CatProxy extends fakeBaseClass<RealCat>() {
private _realCat: RealCat;
constructor(cat: RealCat) {
super();
this._realCat = cat;
let handler = {
get: function(target: CatProxy, prop: keyof RealCat, receiver: any) {
if(RealCat.prototype[prop] !== null) {
return target._realCat[prop];
}
return Reflect.get(target, prop, receiver);
}
};
return new Proxy(this, handler);
}
}
如此一來你可以開始只複寫(override) ICat 介面其中一部分的方法或屬性.